// ignore_for_file: depend_on_referenced_packages

import 'dart:ui';

import 'package:flutter/cupertino.dart' hide Image;
import 'package:flutter/services.dart';
import 'package:flutter_shader/utils/game_loop.dart';
import 'package:vector_math/vector_math.dart';

/// 加载图片
Future<Image> loadImage(String key) async {
  final buffer = await rootBundle.loadBuffer(key);
  final descriptor = await ImageDescriptor.encoded(buffer);
  final codec = await descriptor.instantiateCodec();
  final frameInfo = await codec.getNextFrame();
  return frameInfo.image;
}

Future<FragmentShader> _loadShader(String key) async {
  final program = await FragmentProgram.fromAsset(key);
  return program.fragmentShader();
}

class _ShaderScope extends InheritedWidget {
  const _ShaderScope({
    Key? key,
    required this.shader,
    required Widget child,
  }) : super(key: key, child: child);

  final FragmentShader shader;

  static _ShaderScope of(BuildContext context) {
    final _ShaderScope? result = context.dependOnInheritedWidgetOfExactType<_ShaderScope>();
    assert(result != null, 'No _ShaderScope found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(_ShaderScope old) {
    return shader != old.shader;
  }
}

/// [Shadertoy]构建参数
extension type ShaderBuildContext(BuildContext _context) implements BuildContext {
  _ShaderScope get _scope => _ShaderScope.of(_context);

  /// shader
  FragmentShader get shader => _scope.shader;
}

/// Created by changlei on 2024/3/22.
///
/// 模拟[https://www.shadertoy.com]
class Shadertoy extends StatefulWidget {
  /// 模拟[https://www.shadertoy.com]
  const Shadertoy({
    Key? key,
    required this.assetKey,
    required this.loop,
    this.channelTime,
    this.channelResolution,
    this.channel0,
    this.channel1,
    this.channel2,
    this.channel3,
    required this.builder,
  }) : super(key: key);

  /// 资源key
  final String assetKey;

  /// 时间
  final GameLoop loop;

  /// [Shadertoy.channelTime]
  final double? channelTime;

  /// [Shadertoy.channelResolution]
  final Vector3? channelResolution;

  /// [Shadertoy.channel0]
  final Image? channel0;

  /// [Shadertoy.channel1]
  final Image? channel1;

  /// [Shadertoy.channel2]
  final Image? channel2;

  /// [Shadertoy.channel3]
  final Image? channel3;

  /// 构建
  final Widget Function(ShaderBuildContext context) builder;

  @override
  State<Shadertoy> createState() => _ShadertoyState();
}

class _ShadertoyState extends State<Shadertoy> {
  FragmentShader? _shader;

  double _time = 0.0;

  Future<void> _initialize() async {
    final newShader = await _loadShader(widget.assetKey);
    final oldShader = _shader;
    _shader = null;
    oldShader?.dispose();
    _shader = newShader;
    if (!mounted) {
      return;
    }
    setState(() {});
    _initialized();
  }

  void _initialized() {
    _updateChannelTime();
    _updateChannelResolution();
    _updateChannel0();
    _updateChannel1();
    _updateChannel2();
    _updateChannel3();
    _updateDate();
  }

  @override
  void initState() {
    super.initState();
    widget.loop.addListener(_update);
    _initialize();
  }

  @override
  void didUpdateWidget(covariant Shadertoy oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.loop != oldWidget.loop) {
      oldWidget.loop.removeListener(_update);
      widget.loop.addListener(_update);
    }
    if (widget.assetKey != oldWidget.assetKey) {
      _initialize();
    }
    if (widget.channelTime != oldWidget.channelTime) {
      _updateChannelTime();
    }
    if (widget.channelResolution != oldWidget.channelResolution) {
      _updateChannelResolution();
    }
    if (widget.channel0 != oldWidget.channel0) {
      _updateChannel0();
    }
    if (widget.channel1 != oldWidget.channel1) {
      _updateChannel1();
    }
    if (widget.channel2 != oldWidget.channel2) {
      _updateChannel2();
    }
    if (widget.channel3 != oldWidget.channel3) {
      _updateChannel3();
    }
  }

  void _update() {
    _time += widget.loop.dt;
    try {
      _updateTime();
      _updateTimeDelta();
      _updateDate();
    } finally {
      setState(() {});
    }
  }

  // iResolution
  void _updateResolution(Size size) {
    _shader?.setFloat(0, size.width);
    _shader?.setFloat(1, size.height);
  }

  // iTime
  void _updateTime() {
    _shader?.setFloat(2, _time);
  }

  // iTimeDelta
  void _updateTimeDelta() {
    _shader?.setFloat(3, widget.loop.dt);
  }

  // iChannelTime
  void _updateChannelTime() {
    final channelTime = widget.channelTime;
    if (channelTime != null) {
      _shader?.setFloat(4, channelTime);
    }
  }

  // iChannelResolution
  void _updateChannelResolution() {
    final channelResolution = widget.channelResolution;
    if (channelResolution != null) {
      _shader?.setFloat(5, channelResolution.x);
      _shader?.setFloat(6, channelResolution.y);
      _shader?.setFloat(7, channelResolution.z);
    }
  }

  // iChannel0
  void _updateChannel0() {
    final channel0 = widget.channel0;
    if (channel0 != null) {
      _shader?.setImageSampler(0, channel0);
    }
  }

  // iChannel1
  void _updateChannel1() {
    final channel1 = widget.channel1;
    if (channel1 != null) {
      _shader?.setImageSampler(1, channel1);
    }
  }

  // iChannel2
  void _updateChannel2() {
    final channel2 = widget.channel2;
    if (channel2 != null) {
      _shader?.setImageSampler(2, channel2);
    }
  }

  // iChannel3
  void _updateChannel3() {
    final channel3 = widget.channel3;
    if (channel3 != null) {
      _shader?.setImageSampler(3, channel3);
    }
  }

  // iMouse.xy
  void _updateMouse(Offset position) {
    _shader?.setFloat(8, position.dx);
    _shader?.setFloat(9, position.dy);
  }

  // iMouse.zw
  void _updateClick(Offset position) {
    _shader?.setFloat(10, position.dx);
    _shader?.setFloat(11, position.dy);
  }

  // iDate
  void _updateDate() {
    final dateTime = DateTime.now();
    final duration = Duration(
      hours: dateTime.hour,
      minutes: dateTime.minute,
      seconds: dateTime.second,
      milliseconds: dateTime.millisecond,
      microseconds: dateTime.microsecond,
    );
    _shader?.setFloat(12, dateTime.year.toDouble());
    _shader?.setFloat(13, dateTime.month.toDouble());
    _shader?.setFloat(14, dateTime.day.toDouble());
    _shader?.setFloat(15, duration.inSeconds.toDouble());
  }

  void _onEnter(PointerEnterEvent event) {
    _updateMouse(event.localPosition);
  }

  void _onExit(PointerExitEvent event) {
    _updateMouse(event.localPosition);
  }

  void onHover(PointerHoverEvent event) {
    _updateMouse(event.localPosition);
  }

  void _onTapDown(TapDownDetails details) {
    _updateClick(details.localPosition);
  }

  void _onTapUp(TapUpDetails details) {
    _updateClick(details.localPosition);
  }

  void _onTapCancel() {}

  @override
  void dispose() {
    widget.loop.removeListener(_update);
    _shader?.dispose();
    _shader = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final shader = _shader;
    final Widget child;
    if (shader == null) {
      child = const CupertinoActivityIndicator(
        radius: 24,
      );
    } else {
      child = MouseRegion(
        hitTestBehavior: HitTestBehavior.opaque,
        onEnter: _onEnter,
        onExit: _onExit,
        onHover: onHover,
        cursor: SystemMouseCursors.forbidden,
        child: GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTapDown: _onTapDown,
          onTapUp: _onTapUp,
          onTapCancel: _onTapCancel,
          child: ShaderMask(
            shaderCallback: (bounds) {
              _updateResolution(bounds.size);
              return shader;
            },
            blendMode: BlendMode.srcATop,
            child: _ShaderScope(
              shader: shader,
              child: Builder(
                builder: (context) {
                  return widget.builder(ShaderBuildContext(context));
                },
              ),
            ),
          ),
        ),
      );
    }
    return Center(
      child: child,
    );
  }
}

/// Created by changlei on 2024/2/23.
///
/// 构建[FragmentShader]
class ShaderBuilder extends StatefulWidget {
  /// 构建[FragmentShader]
  const ShaderBuilder({
    super.key,
    required this.assetKey,
    this.child,
    required this.builder,
  });

  /// [FragmentProgram.fromAsset]
  final String assetKey;

  /// child
  final Widget? child;

  /// 构建
  final Widget Function(BuildContext context, FragmentShader? shader, Widget? child) builder;

  @override
  State<ShaderBuilder> createState() => _ShaderBuilderState();
}

class _ShaderBuilderState extends State<ShaderBuilder> {
  FragmentShader? _shader;

  @override
  void initState() {
    super.initState();
    _initialize();
  }

  @override
  void didUpdateWidget(covariant ShaderBuilder oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.assetKey != widget.assetKey) {
      _initialize();
    }
  }

  @override
  void dispose() {
    _shader?.dispose();
    _shader = null;
    super.dispose();
  }

  void _initialize() {
    _loadShader(widget.assetKey).then((value) {
      final shader = _shader;
      _shader = null;
      shader?.dispose();
      if (mounted) {
        setState(() {
          _shader = value;
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, _shader, widget.child);
  }
}
